home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / TSR / TSRSRC35 / RELEASE.PAS < prev    next >
Pascal/Delphi Source File  |  1993-10-21  |  23KB  |  711 lines

  1. {**************************************************************************
  2. *   RELEASE - Releases memory above the last MARK call made.              *
  3. *   Copyright (c) 1986,1993 Kim Kokkonen, TurboPower Software.            *
  4. *   May be freely distributed and used but not sold except by permission. *
  5. ***************************************************************************
  6. *   Version 1.0 2/8/86                                                    *
  7. *     original public release                                             *
  8. *     (thanks to Neil Rubenking for an outline of the method used)        *
  9. *   :                                                                     *
  10. *   long intervening history                                              *
  11. *   :                                                                     *
  12. *   Version 3.0 9/24/91                                                   *
  13. *     make compatible with DOS 5                                          *
  14. *     add Quiet option                                                    *
  15. *     close open file handles of released blocks                          *
  16. *     update for new WATCH behavior                                       *
  17. *     increase number of supported memory blocks to 256                   *
  18. *     add support for upper memory blocks                                 *
  19. *   Version 3.1 11/4/91                                                   *
  20. *     no change                                                           *
  21. *   Version 3.2 11/22/91                                                  *
  22. *     generalize method of accessing high memory                          *
  23. *     reverse order in which memory blocks are released to work           *
  24. *       correctly with the 386MAX high memory manager                     *
  25. *     merge blocks in high memory after release (QEMM doesn't)            *
  26. *   Version 3.3 1/8/92                                                    *
  27. *     add /H to use high memory optionally                                *
  28. *     new features for parsing and getting command line options           *
  29. *   Version 3.4 2/14/92                                                   *
  30. *     fix hang that occurs when QEMM LOADHI didn't have space to          *
  31. *       load a mark high                                                  *
  32. *   Version 3.5 10/18/93                                                  *
  33. *     modify RestoreEMSMap to deal with EMS blocks for which a mapping    *
  34. *       context has been stored                                           *
  35. *     solve problem with RELEASE /U for a MARK loaded high with QEMM 7.0  *
  36. *     solve problem with RELEASE /U for a MARK loaded high with 386MAX    *
  37. ***************************************************************************
  38. *   telephone: 719-260-6641, CompuServe: 76004,2611.                      *
  39. *   requires Turbo version 6 or 7 to compile.                             *
  40. ***************************************************************************}
  41.  
  42. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  43. {$M 16384,0,655360}
  44.  
  45. program ReleaseTSR;
  46.   {-Restore system to state it had when a MARK was placed}
  47.  
  48. uses
  49.   Dos,
  50.   MemU,
  51.   Ems,
  52.   Xms;
  53.  
  54. var
  55.   Blocks : BlockArray;
  56.   markBlock, BlockMax : BlockType;
  57.   markPsp : Word;
  58.   StartMcb : Word;
  59.   HiMemSeg : Word;
  60.  
  61.   markName : String[127];
  62.  
  63.   ReturnCode : Word;
  64.   OptUseHiMem, UseHiMem, DealWithEMS, KeepMark,
  65.   MemMark, FilMark, Quiet : Boolean;
  66.   Keys : string[16];
  67.  
  68.   TrappedBytes : LongInt;
  69.  
  70.   MarkEHandles : Word;
  71.   CurrEHandles : Word;
  72.   MarkEmsHandles : PageArrayPtr;
  73.   CurrEmsHandles : PageArrayPtr;
  74.  
  75.   {Save areas read in from file mark}
  76.   Vectors : array[0..1023] of Byte;
  77.   EGAsavTable : array[0..7] of Byte;
  78.   IntComTable : array[0..15] of Byte;
  79.   ParentSeg : Word;
  80.   ParentLen : Word;
  81.   McbP : ^McbGroup;
  82.  
  83.   procedure Abort(msg : String);
  84.     {-Halt in case of error}
  85.   begin
  86.     WriteLn(msg);
  87.     Halt(1);
  88.   end;
  89.  
  90.   procedure NoRestoreHalt(ReturnCode : Word);
  91.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  92.   begin
  93.     Close(Output);
  94.     asm
  95.       mov ah,$4C
  96.       mov al, byte(ReturnCode)
  97.       int $21
  98.     end;
  99.   end;
  100.  
  101.   function FindMark(markName, MarkID : String;
  102.                     MarkOffset : Word;
  103.                     var MemMark, FilMark : Boolean;
  104.                     var b : BlockType) : Boolean;
  105.     {-Find the last memory block matching idstring at offset idoffset}
  106.   var
  107.     BPsp : Word;
  108.     PassedFileMark : Boolean;
  109.  
  110.     function HasIDstring(segment : Word;
  111.                          idString : String;
  112.                          idOffset : Word) : Boolean;
  113.       {-Return true if idstring is found at segment:idoffset}
  114.     var
  115.       len : Byte;
  116.       tString : String;
  117.     begin
  118.       len := Length(idString);
  119.       tString[0] := Chr(len);
  120.       Move(Mem[segment:idOffset], tString[1], len);
  121.       HasIDstring := (tString = idString);
  122.     end;
  123.  
  124.     function GetMarkName(segment : Word) : String;
  125.       {-Return a cleaned up mark name from the segment's PSP}
  126.     var
  127.       tString : String;
  128.       tlen : Byte absolute tString;
  129.     begin
  130.       Move(Mem[segment:$80], tString[0], 128);
  131.       while (tlen > 0) and ((tString[1] = ' ') or (tString[1] = ^I)) do
  132.         Delete(tString, 1, 1);
  133.       while (tlen > 0) and ((tString[tlen] = ' ') or (tString[tlen] = ^I)) do
  134.         dec(tlen);
  135.       GetMarkName := StUpcase(tString);
  136.     end;
  137.  
  138.     function MatchMemMark(segment : Word;
  139.                           markName : String;
  140.                           var b : BlockType) : Boolean;
  141.       {-Return true if MemMark is unnamed or matches current name}
  142.     var
  143.       FoundIt : Boolean;
  144.       tString : String;
  145.     begin
  146.       tString := GetMarkName(segment);
  147.       if markName <> '' then begin
  148.         FoundIt := (tString = markName);
  149.         if not FoundIt and not UseHiMem then
  150.           if (tString <> '') and (tString[1] = ProtectChar) then
  151.             {Current mark is protected, stop searching}
  152.             b := 1;
  153.       end else if (tString <> '') and (tString[1] = ProtectChar) then begin
  154.         {Stored mark name is protected}
  155.         FoundIt := False;
  156.         {Stop checking}
  157.         b := 1;
  158.       end else if tString = '' then
  159.         {Unnamed release and unnamed mark}
  160.         FoundIt := True
  161.       else begin
  162.         {Unnamed release and named mark, match only if didn't pass file mark}
  163.         FoundIt := not PassedFileMark;
  164.         {Stop searching if no match}
  165.         if not FoundIt then
  166.           B := 1;
  167.       end;
  168.       if not FoundIt then
  169.         dec(b);
  170.       MatchMemMark := FoundIt;
  171.     end;
  172.  
  173.     function MatchFilMark(segment : Word;
  174.                           markName : String;
  175.                           var b : BlockType) : Boolean;
  176.       {-Return true if FilMark is unnamed or matches current name}
  177.     var
  178.       FoundIt : Boolean;
  179.     begin
  180.       if markName <> '' then begin
  181.         FoundIt := (GetMarkName(segment) = markName);
  182.         if FoundIt then
  183.           {Assure named file exists}
  184.           FoundIt := ExistFile(markName);
  185.       end else begin
  186.         {File marks must be named on RELEASE command line}
  187.         FoundIt := False;
  188.         PassedFileMark := True;
  189.       end;
  190.       if not FoundIt then
  191.         dec(B);
  192.       MatchFilMark := FoundIt;
  193.     end;
  194.  
  195.   begin
  196.     {Scan from the last block down to find the last MARK TSR}
  197.     b := BlockMax;
  198.     MemMark := False;
  199.     FilMark := False;
  200.     PassedFileMark := False;
  201.     repeat
  202.       BPsp := Blocks[B].Psp;
  203.       if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  204.         {Don't match any non-program block or this program}
  205.         dec(b)
  206.       else if HasIDstring(BPsp, NmarkID, NmarkOffset) then begin
  207.         {A net mark, can't release it here}
  208.         if UseHiMem then
  209.           {Keep looking}
  210.           dec(b)
  211.         else
  212.           {Stop looking}
  213.           b := 0;
  214.       end else if HasIDstring(BPsp, MarkID, MarkOffset) then
  215.         {An in-memory mark}
  216.         MemMark := MatchMemMark(BPsp, markName, b)
  217.       else if HasIDstring(BPsp, FmarkID, FmarkOffset) then
  218.         {A file mark}
  219.         FilMark := MatchFilMark(BPsp, markName, b)
  220.       else
  221.         {Not a mark}
  222.         dec(b);
  223.     until (b < 1) or MemMark or FilMark;
  224.     FindMark := MemMark or FilMark;
  225.   end;
  226.  
  227.   procedure ReadMarkFile(markName : String);
  228.     {-Read the mark file info into memory}
  229.   var
  230.     McbCount : Word;
  231.     f : file;
  232.   begin
  233.     Assign(f, markName);
  234.     Reset(f, 1);
  235.     if IoResult <> 0 then
  236.       Abort('Error opening mark file');
  237.  
  238.     {Read the vector table from the mark file, into a temporary memory area}
  239.     BlockRead(f, Vectors, 1024);
  240.  
  241.     {Read the BIOS miscellaneous save areas into temporary tables}
  242.     BlockRead(f, EGAsavTable, 8);
  243.     BlockRead(f, IntComTable, 16);
  244.     BlockRead(f, ParentSeg, 2);
  245.     BlockRead(f, ParentLen, 2);
  246.  
  247.     {Read the stored EMS handles, if any}
  248.     BlockRead(f, MarkEHandles, SizeOf(Word));
  249.     GetMem(MarkEmsHandles, SizeOf(HandlePageRecord)*MarkEHandles);
  250.     BlockRead(f, MarkEmsHandles^, SizeOf(HandlePageRecord)*MarkEHandles);
  251.  
  252.     {Read the stored Mcb table}
  253.     BlockRead(f, McbCount, SizeOf(Word));
  254.     GetMem(McbP, SizeOf(Word)+2*SizeOf(Word)*McbCount);
  255.     BlockRead(f, McbP^.Mcbs, 2*SizeOf(Word)*McbCount);
  256.     McbP^.Count := McbCount;
  257.  
  258.     if IoResult <> 0 then
  259.       Abort('Error reading mark file');
  260.     Close(f);
  261.  
  262.     if not KeepMark then
  263.       {Delete the mark file so it causes no mischief later}
  264.       Erase(f);
  265.   end;
  266.  
  267.   procedure InitMarkInfo;
  268.     {-Set up information from mark in memory}
  269.   begin
  270.     MarkEHandles := MemW[markPsp:EMScntOffset];
  271.     MarkEmsHandles := Ptr(markPsp, EMSmapOffset);
  272.     McbP := Ptr(markPsp, EMSmapOffset+4*MarkEHandles);
  273.   end;
  274.  
  275.   procedure CopyVectors;
  276.     {-Put interrupt vectors back into table}
  277.   var
  278.     PSeg : Word;
  279.     PLen : Word;
  280.   begin
  281.     IntsOff;
  282.  
  283.     {Restore the main interrupt vector table}
  284.     if FilMark then
  285.       Move(Vectors, Mem[0:0], 1024)
  286.     else
  287.       Move(Mem[markPsp:VectorOffset], Mem[0:0], 1024);
  288.  
  289.     IntsOn;
  290.  
  291.     {Restore misc save areas}
  292.     if FilMark then begin
  293.       Move(EGAsavTable, Mem[$40:$A8], 8);
  294.       Move(IntComTable, Mem[$40:$F0], 16);
  295.       PSeg := ParentSeg;
  296.       PLen := ParentLen;
  297.     end else begin
  298.       Move(Mem[markPsp:EGAsavOffset], Mem[$40:$A8], 8);
  299.       Move(Mem[markPsp:IntComOffset], Mem[$40:$F0], 16);
  300.       PSeg := MemW[markPsp:ParentOffset];
  301.       PLen := MemW[markPsp:ParLenOffset];
  302.     end;
  303.  
  304.     {Restore the parent address}
  305.     if ValidPsp(HiMemSeg, PSeg, PLen) then begin
  306.       {Don't restore parent if it no longer exists (applies to QEMM LOADHI)}
  307.       MemW[PrefixSeg:$16] := PSeg;
  308.       if not UseHiMem then
  309.         {Programs loaded into high memory have strange termination addresses}
  310.         Move(Mem[0:4*$22], Mem[PrefixSeg:$0A], 4); {Int 22 addresses}
  311.     end;
  312.  
  313.     {Move the old break/error addresses into this program}
  314.     Move(Mem[0:4*$23], Mem[PrefixSeg:$0E], 8); {Int 23,24 addresses}
  315.   end;
  316.  
  317.   procedure MarkBlocks(markBlock : BlockType);
  318.     {-Mark those blocks to be released}
  319.  
  320.     procedure BatchWarning(b : BlockType);
  321.       {-Warn about the trapping effect of batch files}
  322.     var
  323.       t : BlockType;
  324.     begin
  325.       {WriteLn('Memory space for TSRs installed prior to batch file');} {!!}
  326.       {WriteLn('will not be released until batch file completes.');}    {!!}
  327.       {WriteLn;}                                                        {!!}
  328.       ReturnCode := 1;
  329.       {Accumulate number of bytes temporarily trapped}
  330.       for t := 1 to b do
  331.         if Blocks[t].releaseIt then
  332.           inc(TrappedBytes, LongInt(MemW[Blocks[t].mcb:3]) shl 4);
  333.     end;
  334.  
  335.     procedure MarkBlocksAbove;
  336.       {-Mark blocks above the mark}
  337.     var
  338.       b : BlockType;
  339.     begin
  340.       for b := 1 to BlockMax do
  341.         with Blocks[b] do
  342.           if (b >= markBlock) and (mcb+1 = psp) and (memw[psp:$16] = psp) then begin
  343.             {Don't release blocks owned by master COMMAND.COM}
  344.             releaseIt := False;
  345.             BatchWarning(b);
  346.           end else if KeepMark then
  347.             {Release all but RELEASE and the mark}
  348.             releaseIt := (psp <> PrefixSeg) and (psp > markPsp)
  349.           else
  350.             releaseIt := (psp <> PrefixSeg) and (psp >= markPsp);
  351.     end;
  352.  
  353.     procedure MarkUnallocatedBlocks;
  354.       {-Mark blocks that weren't allocated at time of mark}
  355.     var
  356.       TopSeg : Word;
  357.       b : BlockType;
  358.       m : BlockType;
  359.       Found : Boolean;
  360.     begin
  361.       {Find last low memory mcb}
  362.       TopSeg := TopOfMemSeg-1;
  363.       m := 1;
  364.       Found := False;
  365.       while (not Found) and (m <= McbP^.Count) do
  366.         if McbP^.Mcbs[m].mcb >= TopSeg then
  367.           Found := True
  368.         else
  369.           inc(m);
  370.  
  371.       {Mark out all mcbs associated with psp of last low memory mcb}
  372.       TopSeg := McbP^.Mcbs[m-1].psp;
  373.       if TopSeg <> markPsp then
  374.         for m := 1 to McbP^.Count do
  375.           with McbP^.Mcbs[m] do
  376.             if psp = TopSeg then
  377.               psp := 0;
  378.  
  379.       for b := 1 to BlockMax do
  380.         with Blocks[b] do begin
  381.           Found := False;
  382.           m := 1;
  383.           while (not Found) and (m <= McbP^.Count) do begin
  384.             Found := (McbP^.Mcbs[m].psp <> 0) and (McbP^.Mcbs[m].mcb = mcb);
  385.             inc(m);
  386.           end;
  387.           if Found then
  388.             {was allocated at time of mark, keep it now unless a mark to be released}
  389.             releaseIt := not KeepMark and (psp = markPsp)
  390.           else if (mcb+1 = psp) and (memw[psp:$16] = psp)  then
  391.             {Don't release blocks owned by master COMMAND.COM}
  392.             releaseIt := False
  393.           else if (psp <= $400) or (psp >= $FFF0) then
  394.             {Don't release blocks owned by system or 386MAX}
  395.             releaseIt := False
  396.           else
  397.             {not allocated at time of mark}
  398.             releaseIt := (psp <> PrefixSeg);
  399.         end;
  400.     end;
  401.  
  402.   begin
  403.     if UseHiMem then
  404.       MarkUnallocatedBlocks
  405.     else
  406.       MarkBlocksAbove;
  407.  
  408.     {$IFDEF Debug}
  409.     for b := 1 to BlockMax do
  410.       with Blocks[b] do
  411.         WriteLn(b:3, ' ', HexW(psp), ' ', HexW(mcb), ' ', releaseIt);
  412.     {$ENDIF}
  413.   end;
  414.  
  415.   function ReleaseBlock(Segm : Word) : Word; assembler;
  416.     {-Use DOS services to release memory block}
  417.   asm
  418.     mov ah,$49
  419.     mov es,Segm
  420.     int $21
  421.     jc  @Done
  422.     xor ax,ax
  423. @Done:
  424.   end;
  425.  
  426.   procedure ReleaseMem;
  427.     {-Release DOS memory marked for release}
  428.   var
  429.     B : BlockType;
  430.   begin
  431.     for B := BlockMax downto 1 do
  432.       with Blocks[B] do
  433.         if releaseIt then
  434.           if ReleaseBlock(mcb+1) <> 0 then begin
  435.             WriteLn('Could not release block at segment ', HexW(mcb+1));
  436.             Abort('Memory may be a mess... Please reboot');
  437.           end;
  438.     MergeHiMemBlocks(HiMemSeg);
  439.   end;
  440.  
  441.   procedure SetPSP(PSP : Word); assembler;
  442.     {-Sets current PSP}
  443.   asm
  444.     mov bx,psp
  445.     mov ax,$5000
  446.     int $21
  447.   end;
  448.  
  449.   procedure CloseHandles;
  450.     {-Close any handles of blocks marked for release}
  451.   type
  452.     HandleTable = array[0..65520] of Byte;
  453.   var
  454.     O : Word;
  455.     FileMax : Word;
  456.     TablePtr : ^HandleTable;
  457.     b : BlockType;
  458.     H : Byte;
  459.   begin
  460.     for b := 1 to BlockMax do
  461.       with Blocks[b] do
  462.         if releaseIt and (psp = mcb+1) and (memw[psp:0] = $20CD) then begin
  463.           {A released block with a program segment prefix}
  464.           {set psp to this block}
  465.           setpsp(psp);
  466.  
  467.           {Deal with expanded handle tables in DOS 3.0 and later}
  468.           if DosV >= 3 then begin
  469.             FileMax := MemW[Psp:$32];
  470.             TablePtr := Pointer(MemL[Psp:$34]);
  471.           end else begin
  472.             FileMax := 20;
  473.             TablePtr := Ptr(Psp, $18);
  474.           end;
  475.  
  476.           for O := 0 to FileMax-1 do begin
  477.             H := TablePtr^[O];
  478.             case H of
  479.               0, 1, 2, $FF : {standard handle or not open} ;
  480.             else
  481.               asm
  482.                 mov ah,$3E
  483.                 mov bx,O
  484.                 int $21      {ignore errors}
  485.               end;
  486.             end;
  487.           end;
  488.         end;
  489.  
  490.     {reset psp}
  491.     setpsp(prefixseg);
  492.   end;
  493.  
  494.   procedure RestoreEMSmap;
  495.     {-Restore EMS to state at time of mark}
  496.   var
  497.     O, N, NHandle : Word;
  498.     Status : Byte;
  499.  
  500.     procedure EmsError;
  501.     begin
  502.       WriteLn('Program error or EMS device not responding');
  503.       Abort('EMS memory may be a mess... Please reboot');
  504.     end;
  505.  
  506.     procedure MapAndFree(Handle : Word);
  507.     var
  508.       Status : Byte;
  509.     begin
  510.       Status := FreeEms(NHandle);
  511.       if Status = $86 then
  512.         Status := RestorePageMap(NHandle);
  513.       if Status <> 0 then
  514.         EmsError;
  515.     end;
  516.  
  517.   begin
  518.     {Get the existing EMS page map}
  519.     GetMem(CurrEmsHandles, MaxHandles*SizeOf(HandlePageRecord));
  520.     CurrEHandles := EmsHandles(CurrEmsHandles^);
  521.  
  522.     if CurrEHandles > MaxHandles then
  523.       WriteLn('EMS handle count exceeds capacity of RELEASE -- no action taken')
  524.  
  525.     else if CurrEHandles <> 0 then begin
  526.       {Compare the two maps and deallocate pages not in the stored map}
  527.       for N := 1 to CurrEHandles do begin
  528.         {Scan all current handles}
  529.         NHandle := CurrEmsHandles^[N].Handle;
  530.         if MarkEHandles > 0 then begin
  531.           {See if current handle matches one stored by MARK}
  532.           O := 1;
  533.           while (MarkEmsHandles^[O].Handle <> NHandle) and (O <= MarkEHandles) do
  534.             Inc(O);
  535.           {If not, deallocate the current handle}
  536.           if (O > MarkEHandles) then
  537.             MapAndFree(NHandle);
  538.         end else
  539.           {No handles stored by MARK, deallocate all current handles}
  540.           MapAndFree(NHandle);
  541.       end;
  542.     end;
  543.   end;
  544.  
  545.   procedure GetOptions;
  546.     {-Analyze command line for options}
  547.  
  548.     procedure WriteCopyright;
  549.     begin
  550.       WriteLn('RELEASE ', Version, ', Copyright 1993 TurboPower Software');
  551.     end;
  552.  
  553.     procedure WriteHelp;
  554.       {-Show the options}
  555.     begin
  556.       WriteCopyright;
  557.       WriteLn;
  558.       WriteLn('RELEASE removes memory-resident programs from memory and restores the');
  559.       WriteLn('interrupt vectors to their state as found prior to the installation of a MARK.');
  560.       WriteLn('RELEASE manages both normal DOS memory and also Lotus/Intel Expanded Memory.');
  561.       WriteLn('If WATCH has been installed, RELEASE will update the WATCH data area for the');
  562.       WriteLn('TSRs released.');
  563.       WriteLn;
  564.       WriteLn('RELEASE accepts the following command line syntax:');
  565.       WriteLn;
  566.       WriteLn('  RELEASE [MarkName] [Options]');
  567.       WriteLn;
  568.       WriteLn('Options may be preceded by either / or -. Valid options are as follows:');
  569.       WriteLn;
  570.       WriteLn('  /E         do NOT access EMS memory.');
  571.       WriteLn('  /H         work with upper memory if available.');
  572.       WriteLn('  /K         release memory, but keep the mark in place.');
  573.       WriteLn('  /Q         write no screen output.');
  574.       WriteLn('  /S chars   stuff string (<16 chars) into keyboard buffer on exit.');
  575.       WriteLn('  /U         work with upper memory, but halt if none found.');
  576.       WriteLn('  /?         write this help screen.');
  577.       WriteLn;
  578.       WriteLn('When /U is requested, a MarkName must always be specified.');
  579.       Halt(1);
  580.     end;
  581.  
  582.     procedure GetArgs(S : String);
  583.     var
  584.       SPos : Word;
  585.       Arg : String[127];
  586.     begin
  587.       SPos := 1;
  588.       repeat
  589.         Arg := NextArg(S, SPos);
  590.         if Arg = '' then
  591.           Exit;
  592.         if Arg[1] = '?' then
  593.           WriteHelp
  594.         else if (Arg[1] = '-') or (Arg[1] = '/') then
  595.           case Length(Arg) of
  596.             1 : Abort('Missing command option following '+Arg);
  597.             2 : case UpCase(Arg[2]) of
  598.                   '?' : WriteHelp;
  599.                   'E' : DealWithEMS := False;
  600.                   'H' : OptUseHiMem := True;
  601.                   'K' : KeepMark := True;
  602.                   'Q' : Quiet := True;
  603.                   'S' : begin
  604.                           Arg := NextArg(S, SPos);
  605.                           if Length(Arg) = 0 then
  606.                             Abort('Key string missing');
  607.                           if Length(Arg) > 15 then
  608.                             Abort('No more than 15 keys may be stuffed');
  609.                           Keys := Arg+^M;
  610.                         end;
  611.                   'U' : UseHiMem := True;
  612.                 else
  613.                   Abort('Unknown command option: '+Arg);
  614.                 end;
  615.           else
  616.             Abort('Unknown command option: '+Arg);
  617.           end
  618.         else
  619.           {Named mark}
  620.           markName := StUpcase(Arg);
  621.       until False;
  622.     end;
  623.  
  624.   begin
  625.     {Initialize defaults}
  626.     markName := '';
  627.     Keys := '';
  628.     ReturnCode := 0;
  629.     TrappedBytes := 00;
  630.  
  631.     KeepMark := False;
  632.     Quiet := False;
  633.     DealWithEMS := True;
  634.     UseHiMem := False;
  635.     OptUseHiMem := False;
  636.  
  637.     {Get arguments from the command line and the environment}
  638.     GetArgs(StringPtr(Ptr(PrefixSeg, $80))^);
  639.     GetArgs(GetEnv('RELEASE'));
  640.  
  641.     if not Quiet then
  642.       WriteCopyright;
  643.  
  644.     {Initialize for high memory access}
  645.     if OptUseHiMem or UseHiMem then begin
  646.       HiMemSeg := FindHiMemStart;
  647.       if HiMemSeg = 0 then begin
  648.         if UseHiMem then
  649.           Abort('No upper memory blocks found');
  650.       end else
  651.         UseHiMem := True;
  652.     end else
  653.       HiMemSeg := 0;
  654.  
  655.     if UseHiMem then
  656.       if MarkName = '' then
  657.         Abort('Upper memory releases must refer to named marks');
  658.   end;
  659.  
  660. begin
  661.   {Analyze command line for options}
  662.   GetOptions;
  663.  
  664.   {Get all allocated memory blocks in normal memory}
  665.   FindTheBlocks(True, HiMemSeg, Blocks, BlockMax, StartMcb);
  666.  
  667.   {Find the last one marked with the MARK idstring, and MarkName if specified}
  668.   if not FindMark(markName, MarkID, MarkOffset, MemMark, FilMark, markBlock) then
  669.     Abort('No matching marker found, or protected marker encountered.');
  670.   markPsp := Blocks[markBlock].psp;
  671.  
  672.   {Get file mark information into memory}
  673.   if FilMark then
  674.     ReadMarkFile(markName)
  675.   else
  676.     InitMarkInfo;
  677.  
  678.   {Mark those blocks to be released}
  679.   MarkBlocks(markBlock);
  680.  
  681.   {Copy the vector table from the MARK copy}
  682.   CopyVectors;
  683.  
  684.   {Close open file handles}
  685.   CloseHandles;
  686.  
  687.   {Release normal memory marked for release}
  688.   ReleaseMem;
  689.  
  690.   {Deal with expanded memory}
  691.   if DealWithEMS then
  692.     if EMSpresent then
  693.       RestoreEMSmap;
  694.  
  695.   {Write success message}
  696.   if not Quiet then begin
  697.     Write('Memory released after MARK');
  698.     if markName <> '' then
  699.       Write(' (', markName, ')');
  700.     WriteLn;
  701.     if ReturnCode <> 0 then
  702.       WriteLn(TrappedBytes, ' bytes temporarily trapped until batch file completes');
  703.   end;
  704.  
  705.   {Stuff keyboard buffer if requested}
  706.   if Length(Keys) > 0 then
  707.     StuffKeys(Keys, True);
  708.  
  709.   NoRestoreHalt(ReturnCode);
  710. end.
  711.